use Wx 0.15 qw[:allclasses];
use strict;
package wxSortableGrid;
use Wx qw[:everything];
our @ISA = ("Wx::Grid");

use strict;

#This class provides a grid with the following features:
#* rows and columns are sortable
#* An arbitrary number of end rows are excluded from the sorting
#* only single columns are selectable
#* clicking on any cell selects that cell's column and sets the cursor to that column
#* events are easily disabled (for building up the grid)

sub new {
	#Create self
	my $self = shift;
	$self=$self->SUPER::new(@_);
	bless $self, 'wxSortableGrid';
	
	$self->{ascending} = 1;

	#Register sort events
    Wx::Event::EVT_GRID_LABEL_LEFT_CLICK($self,  \&sort);
	
	$self->CreateGrid(10,10);
	$self->SetSelectionMode(wxGridSelectColumns);
	
	$self->EnableEditing(0);
#	Wx::Event::EVT_GRID_SELECT_CELL($self, \&cell_select);
	Wx::Event::EVT_GRID_RANGE_SELECT($self, \&range_select);
	
	$self->SetSelectionMode(wxGridSelectColumns);
	$self->SetRowLabelAlignment( wxALIGN_RIGHT, wxALIGN_CENTRE );
	$self->SetRowLabelSize(100);
	$self->{omit_rows} = 1;
	
	$self->SetGridCursor(1,1);
	#$self->MoveCursorDown(0);
	#Done
	return $self;
	
	
}

#Disables events being processed
sub disable_events {
	my $self = shift;
	$self->{external_block} = 1;
}

#Enables event processing
sub enable_events {
	my $self = shift;
	$self->{external_block} = 0;
}

#Sets an additional function to be called on an event
sub set_selection_handler {
	my $self = shift;
	my $object = shift;
	$self->{select_handler} = $object;
}

#Select a column 
sub select_column {
	my $self = shift;
	my $colIndex = shift;
	
	$self->{selected_col} = $colIndex;

	$self->SelectCol($self->{selected_col});
	
}

#Select a column by label
sub select_column_by_name {
	my $self = shift;
	my $name = shift;
	
	for (my $index = 0; $index < $self->GetNumberCols(); $index++) {
		if ($self->GetColLabelValue($index) eq $name) {
			$self->select_column($index);
			return;
		}
		
	}
}
#Range selection
sub range_select {
#	print 2;
#	return;
	my $self = shift;
	my $event = shift;

	if ($self->{event_block} || $self->{external_block}) { return; }
	unless ($event->Selecting() ) { return; }
	$self->{event_block} = 1;	

	
#	$self->ClearSelection();

	if (defined $event->GetLeftCol()) {
		$self->{selected_col} = $event->GetLeftCol();
	}
	
	if (defined $self->{selected_col}) {
		$self->SelectCol($self->{selected_col});
		#$self->SetGridCursor(0,$self->{selected_col});
	}


	$self->{event_block} = 0;	
	if ($self->{select_handler}) {
		$self->{select_handler}->display_selected();
	}
}

sub set_sort_direction {
	my $self = shift;
	$self->{ascending} = shift;
}

#Generic sort event handler that re-routes to sortrows and sortcols
sub sort {
	my $self = shift;
	my $event = shift;
	
	my $col = $event->GetCol;
	my $row = $event->GetRow;
	
	unless ($col == -1) { $self->sortrows($col) }
	unless ($row == -1) { $self->sortcols($row) }
	
	if ($col == -1 && $row == -1) {
		$self->sortrows(-1);
	}
}

#Sort rows
sub sortrows {
	my $self = shift;
	my $column = shift;

	my $nrows = $self->GetNumberRows-$self->{omit_rows};
	my $ncols = $self->GetNumberCols;
	my @sortable;
	my %originalPosition;

	#Get the values
	for (my $row = 0; $row < $nrows; $row++) {
		if ($column == -1) {
			$sortable[$row] = $self->GetRowLabelValue($row);
		} else {
			$sortable[$row] = $self->GetCellValue($row,$column);
		}
		push(@{$originalPosition{$sortable[$row]}}, $row);
	}
	
	my @sorted;
	
	#Sort the list
	if ($self->{ascending}) {
		$self->{ascending} = 0;
	    # sort numerically ascending
	    if ($column == -1) {
	    	@sorted = sort { lc($a) cmp lc($b) } @sortable;
	    } else {
	    	@sorted = sort {$a <=> $b} @sortable;
	    }
	} else {
		$self->{ascending} = 1;
	    # sort numerically descending
	    if ($column == -1) {
	    	@sorted = sort { lc($b) cmp lc($a) } @sortable;
	    } else {
	    	@sorted = sort {$b <=> $a} @sortable;
	    }
	}
	
	#Get the new row order
	my @newOrder;
	while (@sorted) {
		push (@newOrder, pop(@{$originalPosition{pop(@sorted)}}));
	}
	

	#We will pop the original values, therefore we need to make a copy for each column
	#my %originalPositionCopy = %{ dclone (\%originalPosition)};

	my @data;
	#Re-order row labels
	for (my $row = 0; $row < $nrows; $row++) { $data[$row] = $self->GetRowLabelValue($row); }
	for (my $row = 0; $row < $nrows; $row++) { $self->SetRowLabelValue($row, $data[$newOrder[$row]]); } 
	
	#Re-order data
	for (my $col = 0; $col < $ncols; $col++) {
		for (my $row = 0; $row < $nrows; $row++) { $data[$row] = $self->GetCellValue($row,$col); }
		for (my $row = 0; $row < $nrows; $row++) { $self->SetCellValue($row, $col, $data[$newOrder[$row]]); } 
	}

}

#Sort columns
sub sortcols {
	my $self = shift;
	
	my $row = shift;

	my $selected = $self->GetSelectedCols();

	my $nrows = $self->GetNumberRows;
	my $ncols = $self->GetNumberCols;
	my @sortable;
	my @colour;
	my %originalPosition;

	#Get the values
	for (my $col = 0; $col < $ncols; $col++) {
		if ($row == -1) {
			$sortable[$col] = $self->GetColLabelValue($col);
		} else {
			$sortable[$col] = $self->GetCellValue($row,$col);
		}
		push(@{$originalPosition{$sortable[$col]}}, $col);
	}
	
	my @sorted;
	
	#Sort the list
	if ($self->{ascending}) {
		$self->{ascending} = 0;
	    # sort numerically ascending
	    if ($row == -1) {
	    	@sorted = sort { lc($a) cmp lc($b) } @sortable;
	    } else {
	    	@sorted = sort {$a <=> $b} @sortable;
	    }
	} else {
		$self->{ascending} = 1;
	    # sort numerically descending
	    if ($row == -1) {
	    	@sorted = sort { lc($b) cmp lc($a) } @sortable;
	    } else {
	    	@sorted = sort {$b <=> $a} @sortable;
	    }
	}
	
	#Get the new row order
	my @newOrder;
	while (@sorted) {
		push (@newOrder, pop(@{$originalPosition{pop(@sorted)}}));
	}
	

	#We will pop the original values, therefore we need to make a copy for each column
	#my %originalPositionCopy = %{ dclone (\%originalPosition)};

	my @data;
	#Re-order col labels
	for (my $col = 0; $col < $ncols; $col++) { $data[$col] = $self->GetColLabelValue($col); }
	for (my $col = 0; $col < $ncols; $col++) { $self->SetColLabelValue($col, $data[$newOrder[$col]]); } 

	#Re-order col widths
	for (my $col = 0; $col < $ncols; $col++) { $data[$col] = $self->GetColSize($col); }
	for (my $col = 0; $col < $ncols; $col++) { $self->SetColSize($col, $data[$newOrder[$col]]); } 
	
	
	#Re-order data
	for (my $row = 0; $row < $nrows; $row++) {
		for (my $col = 0; $col < $ncols; $col++) { $data[$col] = $self->GetCellValue($row,$col); }
		for (my $col = 0; $col < $ncols; $col++) { $self->SetCellValue($row, $col, $data[$newOrder[$col]]); } 
		#Re-order cell background colours
		for (my $col = 0; $col < $ncols; $col++) { $data[$col] = $self->GetCellBackgroundColour($row,$col); }
		for (my $col = 0; $col < $ncols; $col++) { $self->SetCellBackgroundColour($row, $col, $data[$newOrder[$col]]); } 
	}
	#Refresh (in GTK changing the column label widths leaves artefacts until a refresh occurs) 
	$self->Refresh();
	if (defined $selected) { $self->select_column($newOrder[$selected]); }

}

#Set the number of rows to omit on a sort
sub set_row_omit {
	my $self = shift;
	$self->{omit_rows} = shift;
}

